package kaoqin

import (
	"bufio"
	"fmt"
	"logger"
	"math/rand"
	"os"
	"os/exec"
	"panda"
	"strings"
	"time"

	"gopkg.in/yaml.v3"
)

var log = logger.Log

type Device struct {
	device   string
	dataPath string
	config   Config
}

type Config struct {
	Always  bool     `yaml:"always"`
	Running bool     `yaml:"running"`
	Times   []string `yaml:"times"`
}

// RunAlways 函数是一个无限循环的函数，它会不断地读取输入并执行相应的命令。
// 当输入的命令为 "exit" 时，循环会终止。
func RunAlways() {
	for {
		input := panda.Input()
		panda.Cmd(input)
		if input == "exit" {
			break
		}
	}
}

// NewDevice 创建一个新的 Device 实例
// 返回指向新创建的 Device 实例的指针
func NewDevice() *Device {
	d := &Device{
		dataPath: fmt.Sprintf("%s/data.yaml", DataPath),
	}
	d.initialize()
	return d
}

// String 方法返回 Device 对象的字符串表示。
// 该方法将 Device 对象的信息格式化为字符串并返回。
func (d *Device) String() string {
	return fmt.Sprintf("<Device: %s, DataPath: %s, Config: %s, Always: %t, Running: %t>", d.device, d.dataPath, d.config.Times, d.config.Always, d.config.Running)
}

// initialize 函数用于初始化设备
func (d *Device) initialize() {
	d.getDevice()
	d.loadJsonConfig()

	// 确保目录存在
	if err := os.MkdirAll(ImagePath, 0755); err != nil {
		log.Printf("创建目录失败: %v", err)
	}
}

// loadJsonConfig 从指定的 JSON 配置文件中加载配置到 Device 结构体中的 config 字段
func (d *Device) loadJsonConfig() {
	data := panda.FileToJsonMap("D:/Myother/kaoqin/config.json")
	var config Config
	panda.MapToStruct(data, &config)
	d.config = config
}

// loadYamlConfig 加载设备的YAML配置文件
// 从设备的配置文件路径中读取YAML配置文件，并解析到设备的配置中。
// 如果读取或解析失败，则使用默认配置。
// Panda: 速度比较慢，过期不用的函数。
func (d *Device) loadYamlConfig() {
	data, err := os.ReadFile(d.dataPath)
	if err != nil {
		log.Printf("读取配置文件失败,使用默认配置: %v", err)
		d.config = Config{
			Always:  true,
			Running: true,
			Times:   []string{"07:22", "07:32", "12:00", "13:00", "17:32"},
		}
		return
	}

	if err := yaml.Unmarshal(data, &d.config); err != nil {
		log.Printf("解析配置文件失败: %v", err)
	}
}

// ADBcmd 使用adb命令执行传入的命令字符串
//
// 参数：
// cmd - 要执行的adb命令字符串
func (d *Device) ADBcmd(cmd string) {
	log.Warn(cmd)
	cmds := strings.Split(cmd, " ")
	exec.Command(cmds[0], cmds[1:]...).Run()
}

// getDevice 方法用于获取已连接的Android设备序列号
// 返回值：
// - 如果找到已连接的设备，则返回该设备的序列号
// - 如果没有找到已连接的设备，则返回空字符串
func (d *Device) getDevice() string {
	cmd := exec.Command("adb", "devices")
	output, err := cmd.CombinedOutput()
	d.device = ""
	if err != nil {
		log.Printf("Error getting devices: %v\n", err)
		return d.device
	}
	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	// 跳过第一行标题
	if scanner.Scan() {
		for scanner.Scan() {
			line := scanner.Text()
			parts := strings.Split(line, "\t")
			if len(parts) >= 2 && parts[1] == "device" {
				d.device = "-s " + strings.TrimSpace(parts[0])
				log.Printf("Found device: [%s]", d.device)
				return d.device
			}
		}
	}
	log.Println("No connected device found")
	return d.device
}

// Click 在指定的设备上进行点击操作
//
// 参数:
//   - x, y: 点击位置的横纵坐标，类型为int
//   - wait: 每次点击之间的等待时间，类型为time.Duration
//   - times: 点击次数，类型为int
//
// 该方法使用adb命令在指定设备上进行点击操作，每次点击后会等待指定的时间间隔，重复指定次数
func (d *Device) Click(x, y int, wait time.Duration, times int) {
	for i := 0; i < times; i++ {
		str := fmt.Sprintf("adb %s shell input tap %d %d", d.device, x, y)
		d.ADBcmd(str)
		time.Sleep(wait)
	}
}

// Screen 通过 adb 命令对连接的设备进行屏幕截图，并将截图保存到指定路径
//
// 参数:
//
//	imgName - 截图保存的文件名
func (d *Device) Screen(imgName string) {
	cmd := exec.Command("adb", "exec-out", "screencap", "-p")
	outFile, err := os.Create(fmt.Sprintf("%s/%s", ImagePath, imgName))
	if err != nil {
		fmt.Printf("Error creating screenshot file: %v\n", err)
		return
	}
	defer outFile.Close()
	cmd.Stdout = outFile
	err = cmd.Run()
	if err != nil {
		fmt.Printf("Error taking screenshot: %v\n", err)
	}
}

// KeyEvent 函数通过ADB命令向设备发送按键事件
//
// 参数:
//
//	key: 要发送的按键事件名称，例如"HOME"、"BACK"等
func (d *Device) KeyEvent(key string) {
	str := fmt.Sprintf("adb %s shell input keyevent %s", d.device, key)
	d.ADBcmd(str)
	time.Sleep(time.Microsecond * 1000)
}

// KeyCode 函数使用ADB命令模拟按键操作
//
// 参数：
//
//	key string: 要模拟的按键名称，例如"HOME"或"BACK"
func (d *Device) KeyCode(key string) {
	str := fmt.Sprintf("adb %s shell input KECODE_%s", d.device, key)
	d.ADBcmd(str)
}

// Swipe 在设备上模拟滑动操作
//
// x1, y1: 滑动起始位置的 x, y 坐标
// x2, y2: 滑动结束位置的 x, y 坐标
// delay: 滑动过程中的延迟时间（单位：毫秒）
func (d *Device) Swipe(x1, y1, x2, y2 int, delay int) {
	str := fmt.Sprintf("adb %s shell input swipe %d %d %d %d %d", d.device, x1, y1, x2, y2, delay)
	d.ADBcmd(str)
}

// startApp 通过ADB命令启动指定的activity
//
// 参数:
//
//	activity - 需要启动的activity
func (d *Device) startApp(activity string) {
	str := fmt.Sprintf("adb %s shell am start -W %s", d.device, activity)
	d.ADBcmd(str)
}

// InputText 向设备输入文本
//
// 参数:
//
//	text: 要输入的文本字符串
func (d *Device) InputText(text string) {
	str := fmt.Sprintf("adb %s shell input text %s", d.device, text)
	d.ADBcmd(str)
}

// Clear 函数用于清除设备上的数据或状态。
//
// 参数：
//
//	d: *Device类型的指针，表示要操作的设备。
func (d *Device) Clear() {
	d.KeyEvent("224") //亮屏
	d.KeyEvent("187") // 切屏按键 等同于：tap(270, 2275)
	for i := 0; i < 3; i++ {
		d.Swipe(530, 1300, 530, 100, 200)
	}
	d.home()
}

// home 方法将触发设备的 HOME 键事件
func (d *Device) home() {
	d.KeyEvent("KEYCODE_HOME")
}

// back 方法模拟按下设备的返回键
func (d *Device) back() {
	d.KeyEvent("KEYCODE_BACK")
}

// power 方法用于模拟按下电源键
func (d *Device) power() {
	d.KeyEvent("KEYCODE_POWER")
}

// menu 函数是 Device 结构体的一个方法，用于模拟菜单键的按下事件
func (d *Device) menu() {
	d.KeyEvent("KEYCODE_MENU")
}

func (d *Device) Daka_dd(delay bool, click bool) {
	// 检查是否工作日和运行状态
	weekday := time.Now().Weekday()
	if weekday >= time.Monday && weekday <= time.Friday && !d.config.Running {
		if !d.config.Running {
			log.Println("当前设置为不运行状态")
			return
		}
	} else if !d.config.Always {
		log.Println("非工作日且未设置始终运行")
		return
	}

	if delay {
		// 随机延迟
		delaySec := rand.Intn(30) + 30
		log.Printf("等待 %d 秒后打卡...", delaySec)
		time.Sleep(time.Duration(delaySec) * time.Second)
	}

	d.Clear()
	d.startApp("com.alibaba.android.rimet/com.alibaba.android.rimet.biz.LaunchHomeActivity")
	time.Sleep(8 * time.Second)
	d.Click(530, 2050, 1*time.Second, 1) // 工作台
	time.Sleep(5 * time.Second)

	// 尝试查找打卡按钮
	d.Screen("screen.png")
	// 简单的图像匹配逻辑
	x1, y1 := MatchTemplate("D:/Myother/mygo/screen.png", "daka.png")
	d.Click(x1, y1, 3*time.Second, 1)

	if click {
		d.Click(530, 1300, 1*time.Second, 1) // 打卡
		d.Click(50, 1450, 1*time.Second, 1)  // 取消异常打卡
		d.Click(800, 1300, 1*time.Second, 1) // 确认继续打卡
	}

	d.Click(680, 2050, 3*time.Second, 1) // 统计
	d.Swipe(530, 1500, 530, 500, 500)    // 上滑
	time.Sleep(3 * time.Second)
	d.Screen("screen.png")

	// 创建QQ邮箱对象
	mail := panda.QQMail{
		From:    "646318717@qq.com",
		To:      []string{"xiongyf@zih718.com"},
		Subject: "测试邮件",
		// Text:    "这是一封测试邮件的纯文本内容",
		HTML:   "<h1>Hello</h1><p>这是一封测试邮件</p><img src=\"cid:screen.png\">",
		Images: []string{"./screen.png"},
		// Attachments: []string{
		// 	"go.mod",
		// },
		Username: "646318717@qq.com",
		Password: "jjzulinwrppabbhc", // 使用QQ邮箱授权码而非登录密码
	}
	if err := mail.Send(); err != nil {
		panic(err)
	}
}

// scheduleTask 定时执行任务
//
// 参数：
//
//	hour：任务执行的小时（24小时制）
//	minute：任务执行的分钟
//	task：要执行的任务函数
//
// 功能：
//
//	使用goroutine启动一个定时任务，根据指定的时间和频率执行任务函数。
//	如果当前时间已经超过了指定的时间，那么任务将在下一个指定的时间执行。
func scheduleTask(hour, minute int, task func()) {
	go func() {
		for {
			now := time.Now()
			next := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location())
			if now.After(next) {
				next = next.Add(24 * time.Hour)
			}
			time.Sleep(next.Sub(now))
			task()
		}
	}()
}

// parseInt 函数用于将字符串转换为整数
// 参数:
// s: 待转换的字符串
// 返回值:
// int: 如果转换成功，返回转换后的整数；如果转换失败，返回 -1
func parseInt(s string) int {
	var n int
	_, err := fmt.Sscanf(s, "%d", &n)
	if err != nil {
		return -1
	}
	return n
}

func Run() {
	log.Println("Starting Kaoqin application...")

	// 检查ADB是否可用
	if _, err := exec.LookPath("adb"); err != nil {
		log.Fatal("ADB not found in PATH. Please install Android Platform Tools.")
	}

	device := NewDevice()
	if device.device == "" {
		log.Warn("Waiting for device connection...")
		var count = 0
		for device.device == "" && count < 5 {
			time.Sleep(30 * time.Second)
			device.getDevice()
			count++
		}
		if device.device == "" {
			log.Fatal("No connected devices found.")
		}
	}

	// 设置定时打卡任务
	times := device.config.Times
	if len(times) > 0 {
		for _, t := range times {
			parts := strings.Split(t, ":")
			if len(parts) == 2 {
				hour := parseInt(parts[0])
				minute := parseInt(parts[1])
				if hour >= 0 && minute >= 0 {
					scheduleTask(hour, minute, func() { device.Daka_dd(true, false) })
					log.Printf("Scheduled task at %02d:%02d", hour, minute)
				}
			}
		}
	}

	log.Println("Kaoqin application is running...")
	// 保持程序运行
	select {}
}
